﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using UlsStudio.Engine.Database;

namespace UlsStudio.Engine.Parsers
{
    /// <summary>
    /// Converts a file-chunk into ULS objects on seperate thread
    /// </summary>
    internal class FileChunkLoader : IDisposable
    {
        #region Properties

        /// <summary>
        /// File-data to process
        /// </summary>
        private List<FileLoadedUlsLogEntry> FileContentsToProcess { get { return _fileContentsToProcess; } }

        /// <summary>
        /// Parent file-set loader
        /// </summary>
        public UlsFileReader FileLoaderParent
        {
            get { lock (fileLoaderParentLock) { return _ulsLogLoader; } }
        }
        private object fileLoaderParentLock = new object();

        public SQLiteCommand InsertCommand
        {
            get { return _insertCommand; }
        }

        #endregion

        #region Privates & Constructors 

        private readonly List<FileLoadedUlsLogEntry> _fileContentsToProcess = null;
        private readonly UlsFileReader _ulsLogLoader = null;
        private readonly SQLiteCommand _insertCommand = null;

        public FileChunkLoader(List<FileLoadedUlsLogEntry> buffer, UlsFileReader parser, SQLiteCommand insertCommand)
        {
            this._fileContentsToProcess = buffer;
            this._ulsLogLoader = parser;
            this._insertCommand = insertCommand;
        }

        #endregion

        #region SQL Inserts

        /// <summary>
        /// Begin parsing this object into the DB
        /// </summary>
        public void BeginLoadDataChunk()
        {
            FileSetJobLoadingStatus status = ((FileSetJobLoadingStatus)this.FileLoaderParent.FileSetParent.LoadStatuses[this.FileLoaderParent.Settings]);
            int c = status.LoaderThreadCount;

            // Wait until threads free-up to continue
            while (c > Constants.MAX_PARSER_THREADS)
            {
                System.Threading.Thread.Sleep(500);
                
                //Logging.Writeline("Waiting ({0})...", c);
                c = status.LoaderThreadCount;
            }

            // Set thread info in advance to avoid main loop terminating before this new thread has had a chance to get started
            Guid threadId = Guid.NewGuid();

            // Begin parse on seperate thread
            System.Threading.Thread parseThread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(BeginProcessAndInsertToSqlLiteFileData));
            lock (threadsListLock)
            {
                status.LineParsingThreadList.Add(threadId, FileParserThreadStage.DbSorting);
            }
            parseThread.Start(threadId);
        }

        public static object threadsListLock = new object();
        static object insertLockObj = new object();


        void BeginProcessAndInsertToSqlLiteFileData(object threadID)
        {
            if (threadID is Guid) BeginProcessAndInsertToSqlLiteFileData((Guid)threadID);
            else throw new ArgumentOutOfRangeException("threadID");
        }

        /// <summary>
        /// Start processing lines into ULS objects and then into the DB
        /// </summary>
        void BeginProcessAndInsertToSqlLiteFileData(Guid threadId)
        {
            FileSetJobLoadingStatus status = ((FileSetJobLoadingStatus)this.FileLoaderParent.FileSetParent.LoadStatuses[this.FileLoaderParent.Settings]);

            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();

            // Begin parsing text lines into ULS objects
            int insertCount = 0;

            // Convert data to SQL
            if (!status.UserCancelledLoad)
            {
                lock (insertLockObj)
                {
                    foreach (AbstractUlsLogEntry row in this.FileContentsToProcess)
                    {
                        DbHelper.AddLine(row, this.InsertCommand);
                        insertCount++;
                    }
                }
            }
            else
                Logging.Writeline("FileChunkLoader.ProcessLines({0}): User cancelled operation. Aborting inserts.", threadId, sw.Elapsed.ToString());

            // Raise event to file-loader
            OnSegmentComplete(insertCount);

            // Thread done - remove from existence
            lock (threadsListLock)
            {
                Logging.Writeline("FileChunkLoader.ProcessLines({0}): Inserted {2} records.", threadId, sw.Elapsed.ToString(), insertCount);
                status.LineParsingThreadList.Remove(threadId);
            }
            
        }

        #endregion

        #region Event Definitions

        internal class LoadSegmentCompleteEventArgs : EventArgs
        {
            public int LinesInserted { get; set; }
            public LoadSegmentCompleteEventArgs(int linesInserted)
            {
                this.LinesInserted = linesInserted;
            }
        }

        internal delegate void SegmentCompleteDelegate(object sender, LoadSegmentCompleteEventArgs e);

        /// <summary>
        /// The records to be inserted by this object have been completed
        /// </summary>
        internal event SegmentCompleteDelegate SegmentComplete;
        private void OnSegmentComplete(int linesInserted)
        {
            if (SegmentComplete != null)
            {
                SegmentComplete(this, new LoadSegmentCompleteEventArgs(linesInserted));
            }
        }

        #endregion

        public void Dispose() { }
    }
}
